var bytes = require('./src/bytes')
var pako = require('pako')

// 头状态
var HEAD_COMPRESS = 0x01 << 7          //数据压缩
var HEAD_ENCRY = 0x01 << 6          //数据加密
var HEAD_REQ = 0x01 << 5          //请求
var HEAD_URI = 0x01 << 4          //请求路由字符
var HEAD_URI_I = 0x01 << 3          //请求路由压缩
var HEAD_DATA = 0x01 << 2          //请求数据
var HEAD_CRC_MSK_N = 2                  //头数据校验位数
var HEAD_CRC_MSK_M = 0x01 << HEAD_CRC_MSK_N          //头数据校验MOD
var HEAD_CRC_MSK = HEAD_CRC_MSK_M - 1 //头数据校验

// 特殊请求
var REQ_PUSH = 0  // 推送
var REQ_PUSHI = 1  // 推送+id
var REQ_KICK = 2  // 软关闭
var REQ_LAST = 3  // 消息推送检查+
var REQ_LASTC = 4  // 消息推送检查+连续
var REQ_KEY = 5  // 秘钥
var REQ_ACL = 6  // 请求开启
var REQ_BEAT = 7  // 心跳
var REQ_ROUTE = 8  // 路由字典
var REQ_SEND = 9 // 未读、已读消息
var REQ_LOOP = 15 // 连接接受
var REQ_ONEWAY = 16 // 路由处理

// 客户端标识
var FLG_ENCRYPT = 1      // 加密
var FLG_COMPRESS = 1 << 2 // 压缩

var self = {
    libs: {
        bytes: bytes,
        pako: pako,
    },
    exts: {
        // 心跳空闲时间
        beatIdle: 30000,
        // 空闲超时时间 建议 服务端IdleTime x2 或 +30s
        // idleTimeout: 60000,
        // 状态开关
        fCompress: true,
        fEncrypt: true,
        // 最小压缩长度
        compressMin: 256,
        // 转UInt8Array
        buffer(data) {
            if (data instanceof Uint8Array) {
                return data
            }

            if (data instanceof ArrayBuffer || Array.isArray(data)) {
                return new Uint8Array(data)
            }

            return data
        },
        // 压缩
        compress(data) {
            return pako.gzip(this.buffer(data), {})
        },
        // 解压
        uncompress(data) {
            return pako.ungzip(this.buffer(data), {})
        },
        // 秘钥
        encryptKey: null,
        // 加密
        encrypt: bytes.encrypt,
        // 解密
        decrypt: bytes.decrypt,
        // 缓存标识
        id: '',
        // 路由字典
        uriMapHash: "",
        uriMapUriI: {},
        uriIMapUri: {},
        // 读取本地
        readLocal(key) {
            localStorage && localStorage.getItem(key)
        },
        // 保存本地
        saveLocal(key, value) {
            localStorage && localStorage.setItem(key, value)
        },
        setUriMapUriI(uriMapUriI, uriMapHash, save) {
            if (uriMapUriI && uriMapHash) {
                this.uriMapHash = uriMapHash
                this.uriMapUriI = uriMapUriI
                var uriIMapUri = {}
                this.uriIMapUri = uriIMapUri
                for (var uri in uriMapUriI) {
                    uriIMapUri[uriMapUriI[uri]] = uri
                }

                save && this.saveLocal(this.id + '_uriMaps', JSON.stringify([uriMapUriI, uriMapHash]))
            }
        },
        // 初始化
        init(id) {
            this.id = id
            try {
                var uriMaps = this.readLocal(this.id + '_uriMaps')
                uriMaps = uriMaps && JSON.parse(uriMaps)
                if (uriMaps) {
                    this.setUriMapUriI(uriMaps[0], uriMaps[1], false)
                }

            } catch (e) {
                console.error(e)
            }
        },
        crc(head) {
            var h = head >> HEAD_CRC_MSK_N
            return (h << HEAD_CRC_MSK_N) | (h % HEAD_CRC_MSK_M)
        },
        isCrc(head) {
            head = bytes.byte(head)
            return ((head >> HEAD_CRC_MSK_N) % HEAD_CRC_MSK_M) == (head & HEAD_CRC_MSK)
        },
        // 请求 req int32, uri string, uriI int32, data []byte
        req(req, uri, uriI, data, encrypt) {
            var head = 0
            var len = 1
            if (req > 0) {
                // 请求标识
                head |= HEAD_REQ
                len += bytes.getVIntLen(req)
            }

            var uBs = undefined
            var uLen = 0
            if (uri) {
                // 请求路由
                if (!(uriI > 0)) {
                    uriI = this.uriMapUriI[uri]
                    if (uriI > 0) {
                        uri = ""
                    }
                }

                if (uri) {
                    head |= HEAD_URI
                    uBs = bytes.stringToBytes(uri)
                    uLen = uBs.length
                    len += bytes.getVIntLen(uLen) + uLen
                }
            }

            if (uriI > 0) {
                // 请求路由压缩
                head |= HEAD_URI_I
                len += bytes.getVIntLen(uriI)
            }

            var dLen = 0
            if (data && (dLen = data.length) > 0) {
                head |= HEAD_DATA
                if (this.fCompress && dLen > this.compressMin) {
                    // 数据压缩
                    var bs = this.compress(data)
                    var bLen = bs.length
                    if (bLen < dLen) {
                        head |= HEAD_COMPRESS
                        data = bs
                        dLen = bLen
                    }
                }

                if (this.fEncrypt && encrypt && this.encryptKey) {
                    // 数据加密
                    var bs = this.encrypt(data, this.encryptKey)
                    var bLen = bs.length
                    head |= HEAD_ENCRY
                    data = bs
                    dLen = bLen
                }

                len += dLen
            }

            // 头部crc
            head = this.crc(head)
            // 数据准备
            var bs = new Int8Array(len);
            bs[0] = head
            var off = 1
            var setOff = function (v) {
                off = v
            }

            if (req > 0) {
                // 请求标识
                bytes.setVInt(bs, off, req, setOff)
            }

            if (uLen > 0) {
                // 请求路由
                bytes.setVInt(bs, off, uLen, setOff)
                bytes.setBytes(bs, off, uBs, setOff)
            }

            if (uriI > 0) {
                // 请求路由压缩
                bytes.setVInt(bs, off, uriI, setOff)
            }

            if (dLen > 0) {
                // 请求数据
                bytes.setBytes(bs, off, data, setOff)
            }

            return bs
        },
        // 读取
        rep(bs) {
            var head = bs[0]
            if (!this.isCrc(head)) {
                // crc校验失败
                console.error(`HEAD CRC ERR, ${head}`)
                return
            }

            // 数据准备
            var off = 1
            var setOff = function (v) {
                off = v
            }

            var req = 0, uri = "", uriI = 0, data = undefined, pid = 0
            if (head & HEAD_REQ) {
                // 请求标识
                req = bytes.getVInt(bs, off, setOff)
            }

            if (head & HEAD_URI) {
                // 请求路由
                var uLen = bytes.getVInt(bs, off, setOff)
                uri = bytes.bytesToString(bytes.getBytes(bs, off, uLen, setOff))
            }

            if (head & HEAD_URI_I) {
                // 请求路由压缩
                uriI = bytes.getVInt(bs, off, setOff)
            }

            if (req == REQ_PUSHI) {
                // pid参数
                pid = bytes.getInt64(bs, off, setOff)
            }

            if (head & HEAD_DATA) {
                // data参数
                data = bytes.getBytes(bs, off, bs.length - off, setOff)
                if (head & HEAD_ENCRY) {
                    // 解密
                    data = this.decrypt(data, this.encryptKey)
                }

                if (head & HEAD_COMPRESS) {
                    // 解压
                    data = this.uncompress(data)
                }
            }

            if (req < REQ_ONEWAY) {
                // 非返回值 才需要路由压缩解密
                if (!uri && uriI > 0) {
                    // 路由压缩解密
                    uri = this.uriIMapUri[uriI]
                }
            }

            return [req, uri, uriI, data, pid]
        },
        // 检查间隔
        checkDrt: 1000,
        // 请求编号范围
        rqI: 0,
        rqIMax: 16383,
        // 请求队列
        rqAry: [],
        rqDict: {},
        // 创建webSocket
        webSocket(addr) {
            return new WebSocket(addr)
        },
    },
    state: {
        CONN: 'CONN', // 开始连接
        OPEN: 'OPEN', // 连接开启
        LOOP: 'LOOP', // 可以通讯
        CLOSE: 'CLOSE', // 连接关闭
        ERROR: 'ERROR', // 连接错误
        KICK: 'KICK', // 被剔
    },
    /**
     * 
     * @param {*} id  唯一标识(缓存)
     * @param {*} addr 连接地址
     * @param {*} exts 连接扩展 如下定义
     * exts {
     *  beatIdle 心跳空闲
     *  loginData(ws)  返回登录数据
     *  onPush(uri, data, pid)处理推送消息
     *  onLast(gid, connVer, continues)处理消息更新
     *  onState(ws, state, data)处理连接状态变化
     * }
     * @returns 
     */
    newClient(id, addr, exts) {
        exts = Object.assign({}, self.exts, exts)
        exts.init(id)
        exts.addr = addr
        // client 提前定义
        var client
        // 适配器
        var adapter = {
            ws: undefined,
            // cid/unique/gid
            onLoop(ids) {
                var ws = this.ws
                if (!ws || ws.looped) {
                    return
                }

                if (ids) {
                    ids = ids.split('/')
                    ws.cid = ids[0]
                    ws.unique = ids[1]
                    ws.gid = ids[2]
                }

                ws.looped = true
                this.doChecks()
            },
            onRep(rqI, rq, err, data) {
                // [0ws, 1timeout ? (new Date().getTime() + timeout * 1000) : 0, 2false, 3back, 4uri, 5data, 6encrypt, 7rqI, 8req]
                if (!rq) {
                    rq = exts.rqDict[rqI]

                } else if (!rqI) {
                    if (!rq[8]) {
                        rqI = rq[7]
                    }
                }

                if (rq) {
                    // 设置发送回调状态
                    rq[2] = true
                    var back = rq[3]
                    // 删除注册
                    delete exts.rqDict[rqI]
                    if (back) {
                        rq[3] = null
                        if (typeof (back) === 'function') {
                            // 回调调用
                            try {
                                back(err, data)

                            } catch (e) {
                                console.error('rep err ', rqI, e)
                            }
                        }
                    }
                }
            },
            rqAdd(ws, uri, data, encrypt, timeout, back) {
                // [0ws, 1timeout ? (new Date().getTime() + timeout * 1000) : 0, 2false, 3back, 4uri, 5data, 6encrypt, 7rqI, 8req]
                var rq = [ws, timeout ? (new Date().getTime() + timeout * 1000) : 0, false, back, uri, data, encrypt]
                if (back) {
                    // rqI获取
                    var rqI = exts.rqI
                    while (true) {
                        rqI++
                        if (rqI <= REQ_ONEWAY || rqI >= exts.rqIMax) {
                            rqI = REQ_ONEWAY + 1
                        }

                        if (!exts.rqDict[rqI]) {
                            break
                        }
                    }

                    exts.rqI = rqI
                    // push 7rqI
                    rq.push(rqI)
                    exts.rqDict[rqI] = rq
                }

                // 添加到队列
                exts.rqAry.push(rq)
            },
            rqSend(ws, rq) {
                // [0ws, 1timeout ? (new Date().getTime() + timeout * 1000) : 0, 2false, 3back, 4uri, 5data, 6encrypt, 7rqI, 8req]
                if (!ws.looped || rq[2]) {
                    return
                }

                rq[2] = true
                if (!rq[4] && !rq[5]) {
                    // uri 和 data 不存在, 不需要发送， 直接返回
                    if (rq[3]) {
                        this.onRep(undefined, rq, undefined, true)
                    }

                    return
                }

                if (rq[8]) {
                    ws.send(exts.req(rq[8], rq[4], rq[7], rq[5], rq[6]))

                } else {
                    ws.send(exts.req(rq[7] || REQ_ONEWAY, rq[4], 0, rq[5], rq[6]))
                }
            },
            doChecks() {
                var ws = this.ws
                var looped = ws && ws.looped
                var time = new Date().getTime()
                var len = exts.rqAry.length
                for (var i = 0; i < len; i++) {
                    var rq = exts.rqAry[i]
                    if (rq[0] !== ws) {
                        // 请求ws已关闭
                        this.onRep(null, rq, 'closed')

                    } else if (!(rq[1] > time)) {
                        // 请求超时
                        this.onRep(null, rq, 'timeout')

                    } else if (!rq[2] && looped) {
                        // 发送时间
                        ws.idleTime = time
                        // 未发送请求，发送
                        this.rqSend(ws, rq)
                    }

                    if (rq[2] && !rq[3]) {
                        // 已发送返回, 队列删除
                        exts.rqAry.splice(i, 1)
                        len--
                    }
                }

                if (ws) {
                    // 心跳空闲检查
                    if (looped && exts.beatIdle > 0 && ws.lastTime < (time - exts.beatIdle)) {
                        ws.lastTime = time
                        ws.send(exts.req(REQ_BEAT))
                    }

                    // 接收超时检查
                    if (exts.idleTimeout > 0 && ws.lastTime < (time - exts.idleTimeout)) {
                        ws.onerror('idleTimeout')
                    }
                }
            },
        }

        // 定时检查任务
        function checkTimer() {
            if (!exts._checkTimer && exts.checkDrt > 0) {
                exts._checkTimer = setInterval(() => {
                    adapter.doChecks()
                }, exts.checkDrt)
            }
        }

        var client = {
            _exts: exts,
            _adapter: adapter,
            setAddr(addr) {
                exts.addr = addr
            },
            loop(timeout, back) {
                this.req(undefined, undefined, false, timeout, back)
            },
            send(req, uri, uriI, data, encrypt, timeout) {
                if (req <= 0) {
                    return
                }

                this.conn()
                var ws = adapter.ws
                if (!ws) {
                    return
                }

                // 添加到队列
                exts.rqAry.push([ws, timeout ? (new Date().getTime() + timeout * 1000) : 0, false, undefined, uri, data, encrypt, uriI, req])
                // 超时线程
                checkTimer()
                // 触发请求
                if (ws.looped) {
                    adapter.doChecks()
                }
            },
            // 已读消息
            read(gid, lastId, timeout) {
                this.send(REQ_SEND, gid, 0, bytes.int64ToBytes(lastId), false, timeout)
            },
            req(uri, data, encrypt, timeout, back) {
                this.conn()
                var ws = adapter.ws
                if (!ws) {
                    if (typeof (back) === 'function') {
                        back('closed')
                    }

                    return
                }

                // 添加请求
                adapter.rqAdd(ws, uri, data, encrypt, timeout, back)
                // 超时线程
                checkTimer()
                // 触发请求
                if (ws.looped) {
                    adapter.doChecks()
                }
            },
            close() {
                // 关闭超时线程
                if (exts._checkTimer) {
                    clearInterval(exts._checkTimer)
                    delete exts._checkTimer
                }

                // 关闭ws
                if (adapter.ws) {
                    adapter.ws.onclose()
                }

                // 请求检查
                adapter.doChecks()
            },
            conn() {
                if (adapter.ws) {
                    return
                }

                exts.onState(ws, self.state.CONN)
                var ws = exts.webSocket(exts.addr);
                ws.lastTime = new Date().getTime()
                if (exts.idleTime > 0) {
                    checkTimer()
                }

                adapter.ws = ws
                //申请一个WebSocket对象，参数是服务端地址，同http协议使用http://开头一样，WebSocket协议的url使用ws://开头，另外安全的WebSocket协议使用wss://开头
                ws.onopen = function () {
                    //当WebSocket创建成功时，触发onopen事件
                    if (adapter.ws !== ws) {
                        return
                    }

                    console.log("ws onopen", ws);
                    // 客户端状态发送
                    var flag = 0
                    if (exts.fEncrypt) {
                        flag |= FLG_ENCRYPT
                    }

                    if (exts.fCompress) {
                        flag |= FLG_COMPRESS
                    }
                    ws.send(exts.req(0, null, flag, null, false))
                    // 客户端状态监听
                    exts.onState(ws, self.state.OPEN)
                }
                ws.onmessage = function (e) {
                    //当客户端收到服务端发来的消息时，触发onmessage事件，参数e.data包含server传递过来的数据
                    if (adapter.ws !== ws) {
                        ws.close()
                        return
                    }

                    var data
                    if (e instanceof ArrayBuffer) {
                        data = new Uint8Array(e)

                    } else if (e instanceof Uint8Array) {
                        data = e

                    } else if (e instanceof Blob) {
                        bytes.blobLoadResult(e, function (data) {
                            ws.onmessage(data)
                        })

                        return

                    } else {
                        data = e.data
                        if (data && data !== e) {
                            ws.onmessage(data)

                        } else {
                            ws.onerror('onmessage err', e)
                        }

                        return
                    }

                    // rep = [req, uri, uriI, data, pid]
                    var rep = exts.rep(data)
                    if (!rep) {
                        return
                    }

                    try {
                        var req = rep[0]
                        // 接收时间
                        ws.lastTime = new Date().getTime()
                        switch (req) {
                            case REQ_PUSH:
                                // 推送
                                exts.onPush(rep[1], rep[3])
                                return
                            case REQ_PUSHI:
                                // 推送带pid !uri && !data && pid = pid消息发送失败
                                exts.onPush(rep[1], rep[3], rep[4])
                                return
                            case REQ_KICK:
                                // 被踢
                                ws.kicked = true
                                adapter.ws = null
                                ws.close()
                                exts.onState(ws, self.state.KICK, rep[3])
                                return
                            case REQ_LAST:
                                // 最新消息
                                exts.onLast(rep[1], rep[2])
                                return
                            case REQ_LASTC:
                                // 连续消息
                                exts.onLast(rep[1], rep[2], true)
                                return
                            case REQ_KEY:
                                // 秘钥
                                exts.encryptKey = rep[3]
                                return
                            case REQ_ACL:
                                // 登录数据 | 路由字典hash
                                var data = exts.loginData(ws)
                                ws.send(exts.req(0, exts.uriMapHash, 1, data))
                                return
                            case REQ_BEAT:
                                // 心跳
                                return
                            case REQ_ROUTE:
                                // 路由压缩字典
                                var uriMapUriI = JSON.parse(bytes.bytesToString(rep[3]))
                                exts.setUriMapUriI(uriMapUriI, rep[1], true)
                                return
                            case REQ_LOOP:
                                // 连接建立完成 data 为 acl服务器返回用户身份
                                // cid/unique/gid
                                adapter.onLoop(rep[1])
                                exts.onState(ws, self.state.LOOP, rep[3])
                                // 心跳检查
                                if (exts.beatIdle > 0) {
                                    checkTimer()
                                }
                                return
                        }

                        if (rep[2] === 16) {
                            // PROD_SUCC
                            rep[2] = 0
                            if (!rep[3]) {
                                rep[3] = bytes.empty
                            }
                        }

                        if (req > REQ_ONEWAY) {
                            // 请求返回
                            var err = undefined
                            switch (rep[2]) {
                                case 0:
                                    break
                                case 1:
                                    err = 'PROD_NO'
                                    break
                                case 2:
                                    err = 'PROD_ERR'
                                    break
                                default:
                                    err = 'SERV_ERR'
                                    break
                            }

                            adapter.onRep(req, null, err, rep[3])

                        } else {
                            // 保留通道消息处理
                            exts.onReserve(req, rep[1], rep[2], rep[3])
                        }

                    } catch (e) {
                        console.error('onmessage err ', req, e)
                    }
                }
                ws.onclose = function (e) {
                    //当客户端收到服务端发送的关闭连接请求时，触发onclose事件
                    if (adapter.ws !== ws) {
                        return
                    }

                    console.error("ws onclose", ws, e);
                    adapter.ws = undefined
                    exts.onState(ws, self.state.CLOSE, e)
                }
                ws.onerror = function (e) {
                    //如果出现连接、处理、接收、发送数据失败的时候触发onerror事件
                    if (adapter.ws !== ws) {
                        return
                    }

                    console.error("ws onerror", ws, e);
                    adapter.ws = undefined
                    exts.onState(ws, self.state.ERROR, e)
                }

                if (typeof (ws.openConn) === 'function') {
                    ws.openConn()
                }
            },
        }

        return client
    },
}

try {
    self.exts.compress([1])

} catch (e) {
    // 压缩方法不支持
    self.exts.fCompress = false
    console.error(e)
}

module.exports = self
module.exports.axGw = self